Package net.sourceforge.stripes.tag

Source Code of net.sourceforge.stripes.tag.ErrorsTag

/* Copyright 2005-2006 Tim Fennell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sourceforge.stripes.tag;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.controller.StripesConstants;
import net.sourceforge.stripes.controller.StripesFilter;
import net.sourceforge.stripes.util.Log;
import net.sourceforge.stripes.validation.ValidationError;
import net.sourceforge.stripes.validation.ValidationErrors;
import net.sourceforge.stripes.exception.StripesJspException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyTag;
import java.io.IOException;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.SortedSet;
import java.util.TreeSet;

/**
* <p>The errors tag has two modes, one where it displays all validation errors in a list
* and a second mode when there is a single enclosed field-error tag that has no name attribute
* in which case this tag iterates over the body, displaying each error in turn in place
* of the field-error tag.</p>
*
* <p>In the first mode, where the default output is used, it is possible to change the output
* for the entire application using a set of resources in the error messages bundle
* (StripesResources.properties unless you have configured another).  If the properties are
* undefined, the tag will output the text "Validation Errors" in a div with css class errorHeader,
* then output an unordered list of error messages.  The following four resource strings
* (shown with their default values) can be modified to create different default output:</p>
*
* <ul>
*   <li>stripes.errors.header={@literal <div class="errorHeader">Validation Errors</div><ul>}</li>
*   <li>stripes.errors.footer={@literal </ul>}</li>
*   <li>stripes.errors.beforeError={@literal <li>}</li>
*   <li>stripes.errors.afterError={@literal </li>}</li>
* </ul>
*
* <p>The errors tag can also be used to display errors for a single field by supplying it
* with a 'field' attribute which matches the name of a field on the page. In this case the tag
* will display only if errors exist for the named field.  In this mode the tag will first look for
* resources named:</p>
*
* <ul>
*   <li>stripes.fieldErrors.header</li>
*   <li>stripes.fieldErrors.footer</li>
*   <li>stripes.fieldErrors.beforeError</li>
*   <li>stripes.fieldErrors.afterError</li>
* </ul>
*
* <p>If the {@code fieldErrors} resources cannot be found, the tag will default to using the
* same resources and defaults as when displaying for all fields.</p>
*
* <p>Similar to the above, field specific, manner of display the errors tag can also be used
* to output only errors not associated with a field, i.e. global errors.  This is done by setting
* the {@code globalErrorsOnly} attribute to true.</p>
*
* <p>This tag has several ways of being attached to the errors of a specific action request.
* If the tag is inside a form tag, it will display only errors that are associated
* with that form. If supplied with an 'action' attribute, it will display errors only errors
* associated with a request to that URL. Finally, if neither is the case, it
* will always display as described in the paragraph above.</p>
*
* @author Greg Hinkle, Tim Fennell
*/
public class ErrorsTag extends HtmlTagSupport implements BodyTag {

    private static final Log log = Log.getInstance(ErrorsTag.class);

    /** The header that will be emitted if no header is defined in the resource bundle. */
    public static final String DEFAULT_HEADER =
            "<div class=\"errorHeader\">Validation Errors</div><ul>";

    /** The footer that will be emitted if no footer is defined in the resource bundle. */
    public static final String DEFAULT_FOOTER = "</ul>";


    /**
     * True if this tag will display errors, otherwise false. This is determined by the logic
     * laid out in the class level Javadoc around whether this errors tag is for the action
     * that was submitted in the request.
     */
    private boolean display = false;

    /**
     * True if this tag contains a field-error child tag, which controls
     * the place of output of each error
     */
    private boolean nestedErrorTagPresent = false;

    /** Sets the form action for which errors should be displayed. */
    private String action;

    /** An optional attribute that declares a particular field to output errors for. */
    private String field;

    /** An optional attribute that specified to display only the global errors. */
    private boolean globalErrorsOnly;

    /** The collection of errors that match the filtering conditions */
    private SortedSet<ValidationError> allErrors;

    /** An iterator of the list of matched errors */
    private Iterator<ValidationError> errorIterator;

    /** The error displayed in the current iteration */
    private ValidationError currentError;

    /** An index of the error being displayed - zero based */
    private int index = 0;


    /**
     * Called by the IndividualErrorTag to fetch the current error from the set being iterated.
     *
     * @return The error displayed for this iteration of the errors tag
     */
    public ValidationError getCurrentError() {
        this.nestedErrorTagPresent = true;
        return currentError;
    }

    /** Returns true if the error displayed is the first matching error. */
    public boolean isFirst() {
        return (this.allErrors.first() == this.currentError);
    }

    /** Returns true if the error displayed is the last matching error. */
    public boolean isLast() {
        return (this.allErrors.last() == currentError);
    }

    /** Sets the (optional) action of the form to display errors for, if they exist. */
    public void setAction(String action) {
        this.action = action;
    }

    /** Returns the value set with setAction(). */
    public String getAction() {
        return this.action;
    }

    /**
     * Sets the action attribute by figuring out what ActionBean class is identified
     * and then in turn finding out the appropriate URL for the ActionBean.
     *
     * @param beanclass the FQN of an ActionBean class, or a Class object for one.
     */
    public void setBeanclass(Object beanclass) throws StripesJspException {
        String url = getActionBeanUrl(beanclass);
        if (url == null) {
            throw new StripesJspException("The 'beanclass' attribute provided could not be " +
                    "used to identify a valid and configured ActionBean. The value supplied was: " +
                    beanclass);
        }
        else {
            this.action = url;
        }
    }

    /** Sets the (optional) name of a field to display errors for, if errors exist. */
    public void setField(String field) {
        this.field = field;
    }

    /** Gets the value set with setField(). */
    public String getField() {
        return field;
    }

    /** Indicated whether the tag is displaying only global errors. */
    public boolean isGlobalErrorsOnly() { return globalErrorsOnly; }

    /** Tells the tag to display (or not) only global errors and no field level errors. */
    public void setGlobalErrorsOnly(boolean globalErrorsOnly) {
        this.globalErrorsOnly = globalErrorsOnly;
    }

    /**
     * Determines if the tag should display errors based on the action that it is displaying for,
     * and then fetches the appropriate list of errors and makes sure it is non-empty.
     *
     * @return SKIP_BODY if the errors are not to be output, or there aren't any<br/>
     *         EVAL_BODY_TAG if there are errors to display
     */
    @Override
    public int doStartTag() throws JspException {
        HttpServletRequest request = (HttpServletRequest) getPageContext().getRequest();
        ActionBean mainBean = (ActionBean) request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN);
        FormTag formTag = getParentTag(FormTag.class);
        ValidationErrors errors = null;

        // If we are supplied with an 'action' attribute then display the errors
        // only if that action matches the 'action' of the current action bean
        if (getAction() != null) {
            if (mainBean != null) {
                String mainAction = StripesFilter.getConfiguration()
                        .getActionResolver().getUrlBinding(mainBean.getClass());

                if (getAction().equals(mainAction)) {
                    errors = mainBean.getContext().getValidationErrors();
                }
            }
        }
        // Else we don't have an 'action' attribute, so see if we are nested in
        // a form tag
        else if (formTag != null) {
            ActionBean formBean = formTag.getActionBean();
            if (formBean != null) {
                errors = formBean.getContext().getValidationErrors();
            }

        }
        // Else if no name was set, and we're not in a action tag, we're global and ok to display
        else if (mainBean != null) {
            errors = mainBean.getContext().getValidationErrors();
        }

        // If we found some errors that are applicable for display, figure out what to do
        if (errors != null) {
            // Using a set ensures that duplicate messages get filtered out, which can
            // happen during multi-row validation
            this.allErrors = new TreeSet<ValidationError>(new ErrorComparator());

            if (this.field != null) {
                // we're filtering for a specific field
                List<ValidationError> fieldErrors = errors.get(this.field);
                if (fieldErrors != null) {
                    this.allErrors.addAll(fieldErrors);
                }
            }
            else if (this.globalErrorsOnly) {
                List<ValidationError> globalErrors = errors.get(ValidationErrors.GLOBAL_ERROR);
                if (globalErrors != null) {
                    this.allErrors.addAll(globalErrors);
                }
            }
            else {
                for (List<ValidationError> fieldErrors : errors.values()) {
                    if (fieldErrors != null) {
                        this.allErrors.addAll(fieldErrors);
                    }
                }
            }
        }

        // Make sure that after all this we really do have some errors
        if (this.allErrors != null && this.allErrors.size() > 0) {
            this.display = true;
            this.errorIterator = this.allErrors.iterator();
            this.currentError = this.errorIterator.next(); // load up the first error
            return EVAL_BODY_BUFFERED;
        }
        else {
            this.display = false;
            return SKIP_BODY;

        }
    }


    /** Sets the context variables for the current error and index */
    public void doInitBody() throws JspException {
        // Apply TEI attributes
        getPageContext().setAttribute("index", this.index);
        getPageContext().setAttribute("error", this.currentError);
    }


    /**
     * Manages iteration, running again if there are more errors to display.  If there is no
     * nested FieldError tag, will ensure that the body is evaluated only once.
     *
     * @return EVAL_BODY_TAG if there are more errors to display, SKIP_BODY otherwise
     */
    public int doAfterBody() throws JspException {
        if (this.display && this.nestedErrorTagPresent && this.errorIterator.hasNext()) {
            this.currentError = this.errorIterator.next();
            this.index++;

            // Reapply TEI attributes
            getPageContext().setAttribute("index", this.index);
            getPageContext().setAttribute("error", this.currentError);
            return EVAL_BODY_BUFFERED;
        }
        else {
            return SKIP_BODY;
        }
    }


    /**
     * Output the error list if this was an empty body tag and we're fully controlling output*
     *
     * @return EVAL_PAGE always
     * @throws JspException
     */
    @Override
    public int doEndTag() throws JspException {
        try {
            JspWriter writer = getPageContext().getOut();

            if (this.display && !this.nestedErrorTagPresent) {
                // Output all errors in a standard format
                Locale locale = getPageContext().getRequest().getLocale();
                ResourceBundle bundle = null;

                try {
                    bundle = StripesFilter.getConfiguration()
                            .getLocalizationBundleFactory().getErrorMessageBundle(locale);
                }
                catch (MissingResourceException mre) {
                    log.warn("The errors tag could not find the error messages resource bundle. ",
                             "As a result default headers/footers etc. will be used. Check that ",
                             "you have a StripesResources.properties in your classpath (unless ",
                             "of course you have configured a different bundle).");
                }

                // Fetch the header and footer
                String header = getResource(bundle, "header", DEFAULT_HEADER);
                String footer = getResource(bundle, "footer", DEFAULT_FOOTER);
                String openElement  = getResource(bundle, "beforeError", "<li>");
                String closeElement = getResource(bundle, "afterError", "</li>");

                // Write out the error messages
                writer.write(header);

                for (ValidationError fieldError : this.allErrors) {
                    String message = fieldError.getMessage(locale);
                    if (message != null && message.length() > 0) {
                        writer.write(openElement);
                        writer.write(message);
                        writer.write(closeElement);
                    }
                }

                writer.write(footer);
            }
            else if (this.display && this.nestedErrorTagPresent) {
                // Output the collective body content
                getBodyContent().writeOut(writer);
            }

            // Reset the instance state in case the container decides to pool the tag
            this.display = false;
            this.nestedErrorTagPresent = false;
            this.allErrors = null;
            this.errorIterator = null;
            this.currentError = null;
            this.index = 0;

            return EVAL_PAGE;
        }
        catch (IOException e) {
            JspException jspe = new JspException("IOException encountered while writing errors " +
                    "tag to the JspWriter.", e);
            log.warn(jspe);
            throw jspe;
        }
    }

    /**
     * Utility method that is used to lookup the resources used for the errors header,
     * footer, and the strings that go before and after each error.
     *
     * @param bundle the bundle to look up the resource from
     * @param name the name of the resource to lookup (prefixes will be added)
     * @param fallback a value to return if no resource can be found
     * @return the value to use for the named resource
     */
    protected String getResource(ResourceBundle bundle, String name, String fallback) {
        if (bundle == null) {
            return fallback;
        }

        String resource = null;
        if (this.field != null) {
            try { resource = bundle.getString("stripes.fieldErrors." + name); }
            catch (MissingResourceException mre) { /* Do nothing */ }
        }

        if (resource == null) {
            try { resource = bundle.getString("stripes.errors." + name); }
            catch (MissingResourceException mre) { resource = fallback; }
        }

        return resource;
    }

    /**
     * Inner class Comparator used to provide a consistent ordering of validation errors.
     * Sorting is done by field name (the programmatic one, not the user visible one). Errors
     * without field names sort to the top since it is assumed that these are global errors
     * as oppose to field specific ones.
     */
    private static class ErrorComparator implements Comparator<ValidationError> {
        public int compare(ValidationError e1, ValidationError e2) {
            // Identical errors should be suppressed
            if (e1.equals(e2)) {
                return 0;
            }

            String fn1 = e1.getFieldName();
            String fn2 = e2.getFieldName();
            boolean e1Global = fn1 == null || fn1.equals(ValidationErrors.GLOBAL_ERROR);
            boolean e2Global = fn2 == null || fn2.equals(ValidationErrors.GLOBAL_ERROR);

            // Sort globals above non-global errors
            if (e1Global && !e2Global) {
                return -1;
            }
            if (e2Global && !e1Global) {
                return 1;
            }
            if (fn1 == null && fn2 == null) {
                return 0;
            }

            // Then sort by field name, and if field names match make the first one come first
            int result = e1.getFieldName().compareTo(e2.getFieldName());
            if (result == 0) {result = 1;}
            return result;
        }
    }
}
TOP

Related Classes of net.sourceforge.stripes.tag.ErrorsTag

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.